/*
 ============================================================================
 Name        : TP1.c
 Author      : guido
 Version     :
 Copyright   : Your copyright notice
 Description :
 ============================================================================
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "Directivas.h"

struct campoDefine {
	char nombre[100];
	char valor[100];
};

typedef struct campoDefine campoDefine;

struct vectorDefiniciones {
	campoDefine definicion[50];
	int ultimoDefine;
};

typedef struct vectorDefiniciones vectorDefiniciones;

const int longitud = 80;
const int limiteDefine = 200;
const int cantidadDeDefines = 50;
const char saltoDeLinea = 10;
const char asterisco = '*';
const char cierreDeComentario[3] = { '*', '/', 0 };
const char caracteresDeSeparacion[13] = { ' ', '*', '+', '-', '/', '%', '<', '>', '=', '(', ')', 10, 0 };
const char finDeLinea = '\n';

int preprocesar(char* ruta);
int parsear(FILE* miArchivo);
int procesarDirectivas(char* buffer, int* ifs);
int borrarComentarios(char* buffer, int* comentarioAbierto);
int validarCaracteres(char* buffer);
int buscarDefinicion(char* identificador, vectorDefiniciones* vector);
int reemplazarDefines(char* buffer, vectorDefiniciones* vector);
int obtenerTokens(char* destino, char* source);
void agregarDefinicion(char* buffer, vectorDefiniciones* vectorDefines);

int main(int argc, char* argv[]) {
	int resultado;
	if (argc == 2) {
		resultado = preprocesar(argv[1]);
		return (resultado);
	} else {
		printf("%s", "Parámetros incorrectos");
		return (1);
	}
}

/* Recibe un archivo y lo procesa devolviéndolo por pantalla
 *
 */
int preprocesar(char* ruta) {
	FILE* vectorDeArchivos[10];
	int archivoActual = 0;
	vectorDeArchivos[archivoActual] = fopen(ruta, "r");
	char buffer[100];
	int comentarioAbierto = 0;
	int resultado = 0;
	int ifs = 0;
	int omitirCodigo = 0;
	int hayDirectiva;
	int elseAbierto = 0;
	vectorDefiniciones vectorDefines;
	vectorDefines.ultimoDefine = 0;

	if (vectorDeArchivos[archivoActual] != NULL) {
		int quedanArchivos = 1;
		void* primerLeida = fgets(buffer, longitud, vectorDeArchivos[archivoActual]);

		while ((quedanArchivos) && (primerLeida)) {
			borrarComentarios(buffer, &comentarioAbierto);

			hayDirectiva = 0;

			if (hayDirectivaElse(buffer)) {
				hayDirectiva = 1;
				if (elseAbierto)
					return 1;
				omitirCodigo = !omitirCodigo;
				elseAbierto = 1;
			}

			else if (hayDirectivaEndif(buffer)) {
				hayDirectiva = 1;
				omitirCodigo = 0;
				ifs--;
				elseAbierto = 0;
			}

			else if (!(omitirCodigo)) {
				if (hayDirectivaIfndef(buffer) || hayDirectivaIfdef(buffer)) {
					elseAbierto = 0;
					hayDirectiva = 1;
					if (ifs == 15)
						return 1;
					ifs++;

					char* identificador;
					char* token;

					char temp[100];
					strcpy(temp, buffer);

					token = strtok(temp, " \n");
					token = strtok(NULL, " \n");
					identificador = token;

					// Sin argumentos
					if (identificador == NULL
						)
						return 1;
					// Mas de una palabra
					if (strtok(NULL, " \n") != NULL) {
						return 1;
					}
					validarCaracteres(identificador);
					int indice = buscarDefinicion(identificador, &vectorDefines);

					if (hayDirectivaIfndef(buffer)) {
						if (indice < 0)
							omitirCodigo = 0;
						else
							omitirCodigo = 1;
					}

					else if (hayDirectivaIfdef(buffer)) {
						if (indice < 0)
							omitirCodigo = 1;
						else
							omitirCodigo = 0;
					}

				} else if (hayDirectivaInclude(buffer)) {
					hayDirectiva = 1;
					char archivo[100];
					strcpy(archivo, get_current_dir_name());
					strcat(archivo, "/");
					char* token;
					token = strtok(buffer + longInclude, "\"");
					strcat(archivo, token);
					archivoActual++;
					vectorDeArchivos[archivoActual] = fopen(archivo, "r");
					if (vectorDeArchivos[archivoActual] == NULL) {
						printf("%s", "Archivo inexistente");
						return 1;
					}

				} else if (hayDirectivaDefine(buffer)) {
					hayDirectiva = 1;
					if (strlen(buffer) > limiteDefine)
						return 1;
					else {
						if (validarCaracteres(buffer) != 0) {
							return 1;
						} else {
							agregarDefinicion(buffer, &vectorDefines);
						}
					}
				}
			}
			if ((hayDirectiva) || (omitirCodigo)) {
				buffer[0] = finDeLinea;
				buffer[1] = 0;
			} else
				reemplazarDefines(buffer, &vectorDefines);

			printf("%s", buffer);

			if (fgets(buffer, longitud, vectorDeArchivos[archivoActual]) == NULL) {
				fclose(vectorDeArchivos[archivoActual]);
				archivoActual--;
				if (archivoActual == -1) {
					quedanArchivos = 0;
				}
			}
		}
		if (comentarioAbierto) {
			resultado = 1;
		}
		return (resultado);
	} else {
		printf("%s", "Archivo inexistente");
		return 1;
	}
	return (resultado);
}

int validarCaracteres(char* buffer) {
	return 0;
}

/* El autor reconoce el potencial peligro de usar break's de forma
 * indiscriminada pero cree que su uso en la siguiente funcion
 * es apropiado ya que aumenta la legibilidad */
int buscarDefinicion(char* identificador, vectorDefiniciones* vector) {
	int i;
	for (i = 0; i < cantidadDeDefines; i++) {
		if (strcmp(identificador, (*vector).definicion[i].nombre) == 0) {
			break;
		}
	}
	if (i == 50)
		i = -1;

	return i;
}

int reemplazarDefines(char* buffer, vectorDefiniciones* vector) {
	char token[50];
	char temp[100];
	char resultado[100];
	resultado[0] = 0;
	strcpy(temp, buffer);
	int huboDefinicion = 0;

	int tipoDeToken;
	tipoDeToken = obtenerTokens(token, temp);
	while (tipoDeToken > 0) {
		if (tipoDeToken == 1) {
			int pos = buscarDefinicion(token, vector);
			if (pos >= 0) {
				if ((*vector).definicion[pos].valor[0] != 0) {
					strcpy(token, (*vector).definicion[pos].valor);
					huboDefinicion = 1;
				}
			}
			strcat(resultado, token);
		}
		if (tipoDeToken == 2) {
			strcat(resultado, token);
		}
		tipoDeToken = obtenerTokens(token, temp);
	}

	if (huboDefinicion)
		strcpy(buffer, resultado);
	return 0;
}

/* Esta funcion quita el token mas proximo desde la izquierda, y lo coloca en destino
 * Si retorna 0, la linea esta vacia,
 * 1, retorno un token con caracteres validos
 * 2, retorno un token con caracteres de separaciones
 */
int obtenerTokens(char* destino, char* source) {
	if (source[0] == 0)
		return 0;

	int i = strspn(source, caracteresDeSeparacion);
	if (i == 0) {
		// Caracteres de campo
		int h = strcspn(source, caracteresDeSeparacion);
		strncpy(destino, source, h);
		destino[h] = 0;
		strcpy(source, source + h);
		return 1;
	} else {
		// Caracteres de separacion
		strncpy(destino, source, i);
		destino[i] = 0;
		strcpy(source, source + i);
		return 2;
	}
}

int borrarComentarios(char* buffer, int* comentarioAbierto) {
	int i = 0;
	int tamanio = strlen(buffer);

	while (i + 1 < tamanio) {
		tamanio = strlen(buffer);
		if ((*comentarioAbierto) == 1) {
			int h = i;
			while ((h < tamanio - 1) && ((*comentarioAbierto) == 1)) {
				if (memcmp(cierreDeComentario, buffer + h, 2) == 0) {
					*comentarioAbierto = 0;
				} else {
					h++;
				}
			}
			if ((*comentarioAbierto) == 1) {
				buffer[i] = 0;
			}

			if ((*comentarioAbierto) == 0) {
				strcpy(buffer + i, buffer + h + 2);
			}

		} else if (!(*comentarioAbierto) && (i + 1 < tamanio)) {
			if (buffer[i] == '/') {
				if (buffer[i + 1] == '*') {
					*comentarioAbierto = 1;
					i--;
				} else if (buffer[i + 1] == '/') {
					buffer[i] = 10;
					buffer[i + 1] = 0;
				}
			}

			i++;
		}
	}
	if ((buffer[0] == saltoDeLinea) && (*comentarioAbierto)) {
		buffer[0] = 0;
	}
	return 0;
}

void agregarDefinicion(char* buffer, vectorDefiniciones* vectorDefines) {
	char* token;
	char temp[100];
	int ultimoDefine = (*vectorDefines).ultimoDefine;
	strcpy(temp, buffer);

	token = strtok(temp, " \n");
	token = strtok(NULL, " \n");
	strcpy((*vectorDefines).definicion[ultimoDefine].nombre, token);
	token = strtok(NULL, "\n");
	if (token != NULL) {
		strcpy((*vectorDefines).definicion[ultimoDefine].valor, token);
	}

	(*vectorDefines).ultimoDefine++;
}
